package trinnes.oliver.unimaas.smartpentalath.data;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import trinnes.oliver.unimaas.smartpentalath.exception.PentalathException;

/**
 * Contains all the necessary information and operations on the playing board.
 * @author Oliver Trinnes
 */
public class Board
{
  private List<Cell> cells;
  private Cell[][] grid;
  private int size;
  private static final int MIN_BOARD_SIZE = 1;
  private static final int MAX_BOARD_SIZE = 10;

  public Board(int size)
  {
    this.size = size;
    if(size < MIN_BOARD_SIZE || size > MAX_BOARD_SIZE)
      throw new PentalathException("The valid board size ranges from "
          + MIN_BOARD_SIZE + " to " + MAX_BOARD_SIZE + ".");

    int numberOfCells = 1;
    for(int i = 0; i < size; i++)
      numberOfCells += i * 6;

    grid = new Cell[2 * size - 1][2 * size - 1];
    cells = new ArrayList<Cell>();

    for(int i = 0; i < numberOfCells; i++)
    {
      Cell cell = new Cell();
      cells.add(cell);
    }

    int index = 0;
    for(int row = 0; row < 2 * size - 1; row++)
    {
      if(row < size)
        for(int column = 0; column < size + row; column++)
        {
          grid[row][column] = cells.get(index);
          index++;
        }
      else
        for(int column = row - size + 1; column < 2 * size - 1; column++)
        {
          grid[row][column] = cells.get(index);
          index++;
        }
    }

    // Initialize the neighbors in every direction for every cell
    for(int i = 0; i < grid.length; i++)
    {
      for(int j = 0; j < grid[i].length; j++)
      {
        if(grid[i][j] != null)
        {
          // Add east neighbor
          if(j + 1 < grid[i].length)
            grid[i][j].setNeighbor(Direction.EAST, grid[i][j + 1]);

          // Add south east neighbor
          if(i + 1 < grid.length && j + 1 < grid[i + 1].length)
            grid[i][j].setNeighbor(Direction.SOUTH_EAST, grid[i + 1][j + 1]);

          // Add south west neighbor
          if(i + 1 < grid.length)
            grid[i][j].setNeighbor(Direction.SOUTH_WEST, grid[i + 1][j]);

          // Add west neighbor
          if(j - 1 >= 0)
            grid[i][j].setNeighbor(Direction.WEST, grid[i][j - 1]);

          // Add north west neighbor
          if(i - 1 >= 0 && j - 1 >= 0)
            grid[i][j].setNeighbor(Direction.NORTH_WEST, grid[i - 1][j - 1]);

          // Add north west neighbor
          if(i - 1 >= 0)
            grid[i][j].setNeighbor(Direction.NORTH_EAST, grid[i - 1][j]);
        }
      }
    }
    setCentersDists(cells.get(0), size - 1);
  }

  /*
   * Sets the distances to the center for every cell (needed for evaluation)
   */
  private void setCentersDists(Cell cell, int value)
  {
    cell.setCenterDist(value);
    if(value == 0)
      return;

    Cell neighbor = cell;
    for(int i = 0; i < value; i++)
    {
      neighbor = neighbor.getNeighbor(Direction.EAST);
      neighbor.setCenterDist(value);
    }
    for(int i = 0; i < value; i++)
    {
      neighbor = neighbor.getNeighbor(Direction.SOUTH_EAST);
      neighbor.setCenterDist(value);
    }
    for(int i = 0; i < value; i++)
    {
      neighbor = neighbor.getNeighbor(Direction.SOUTH_WEST);
      neighbor.setCenterDist(value);
    }
    for(int i = 0; i < value; i++)
    {
      neighbor = neighbor.getNeighbor(Direction.WEST);
      neighbor.setCenterDist(value);
    }
    for(int i = 0; i < value; i++)
    {
      neighbor = neighbor.getNeighbor(Direction.NORTH_WEST);
      neighbor.setCenterDist(value);
    }
    for(int i = 0; i < value; i++)
    {
      neighbor = neighbor.getNeighbor(Direction.NORTH_EAST);
      neighbor.setCenterDist(value);
    }
    setCentersDists(neighbor.getNeighbor(Direction.SOUTH_EAST), value - 1);
  }

  /*
   * Puts a piece into a cell on the board
   */
  public void putPiece(Piece piece, int position)
  {
    if(position < 0 || position > cells.size())
      throw new PentalathException("The chosen position \"" + position
          + "\" is not on the board.");
    cells.get(position).setPiece(piece);
  }

  /*
   * Checks if the indicated cell ist not empty
   */
  public boolean isOccupied(int move)
  {
    return !cells.get(move).getPiece().equals(Piece.EMPTY);
  }

  public List<Cell> getCells()
  {
    return cells;
  }

  public Cell[][] getGrid()
  {
    return grid;
  }

  public int getSize()
  {
    return size;
  }

  public boolean isInARow(int move, Piece piece)
  {
    Cell cell = cells.get(move);

    // Temporarily set the piece to validate
    Piece oldPiece = cell.getPiece();
    cell.setPiece(piece);

    if(piece.equals(Piece.EMPTY))
      throw new PentalathException("An empty cell cannot result in a row.");

    // Horizontal check
    int eastWest = getRowSize(move, Direction.EAST, Direction.WEST);
    if(eastWest >= size)
    {
      cell.setPiece(oldPiece);
      return true;
    }

    // First diagonal check
    int northEastSouthWest = getRowSize(move, Direction.NORTH_EAST,
        Direction.SOUTH_WEST);
    if(northEastSouthWest >= size)
    {
      cell.setPiece(oldPiece);
      return true;
    }

    // Second diagonal check
    int southEastNorthWest = getRowSize(move, Direction.SOUTH_EAST,
        Direction.NORTH_WEST);
    if(southEastNorthWest >= size)
    {
      cell.setPiece(oldPiece);
      return true;
    }

    cell.setPiece(oldPiece);
    return false;
  }

  public boolean isInARowWithFreedom(int move, Piece piece, int rowLength)
  {
    Cell cell = cells.get(move);

    // Temporarily set the piece to validate
    Piece oldPiece = cell.getPiece();
    cell.setPiece(piece);

    if(piece.equals(Piece.EMPTY))
      throw new PentalathException("An empty cell cannot result in a row.");

    // Horizontal check
    for(int dir = 0; dir < 3; dir++)
    {
      int oppositeDir = (dir + 3) % 6;
      int rowSizeBothDirs = getRowSize(move, dir, oppositeDir);
      if(rowSizeBothDirs == rowLength)
      {
        Cell neighbor = cells.get(move);
        while(neighbor != null && neighbor.getPiece().equals(cell.getPiece()))
          neighbor = neighbor.getNeighbor(dir);
        if(neighbor == null || !neighbor.getPiece().equals(Piece.EMPTY))
        {
          cell.setPiece(oldPiece);
          return false;
        }

        neighbor = cells.get(move);
        while(neighbor != null && neighbor.getPiece().equals(cell.getPiece()))
          neighbor = neighbor.getNeighbor(oppositeDir);
        if(neighbor == null || !neighbor.getPiece().equals(Piece.EMPTY))
        {
          cell.setPiece(oldPiece);
          return false;
        }

        cell.setPiece(oldPiece);
        return true;
      }
    }
    cell.setPiece(oldPiece);
    return false;
  }

  public int getMaxRowSize(int move, Piece piece)
  {
    Cell cell = cells.get(move);

    // Temporarily set the piece to validate
    Piece oldPiece = cell.getPiece();
    cell.setPiece(piece);
    int max = 0;

    if(piece.equals(Piece.EMPTY))
      throw new PentalathException("An empty cell cannot result in a row.");

    // Horizontal check
    int eastWest = getRowSize(move, Direction.EAST, Direction.WEST);
    if(eastWest > max)
      max = eastWest;

    // First diagonal check
    int northEastSouthWest = getRowSize(move, Direction.NORTH_EAST,
        Direction.SOUTH_WEST);
    if(northEastSouthWest > max)
      max = northEastSouthWest;

    // Second diagonal check
    int southEastNorthWest = getRowSize(move, Direction.SOUTH_EAST,
        Direction.NORTH_WEST);
    if(southEastNorthWest > max)
      max = southEastNorthWest;

    cell.setPiece(oldPiece);
    return max;
  }

  public int getRowSum(int move, Piece piece)
  {
    Cell cell = cells.get(move);

    // Temporarily set the piece to validate
    Piece oldPiece = cell.getPiece();
    cell.setPiece(piece);

    if(piece.equals(Piece.EMPTY))
      throw new PentalathException("An empty cell cannot result in a row.");

    // Horizontal check
    int eastWest = getRowSize(move, Direction.EAST, Direction.WEST);

    // First diagonal check
    int northEastSouthWest = getRowSize(move, Direction.NORTH_EAST,
        Direction.SOUTH_WEST);

    // Second diagonal check
    int southEastNorthWest = getRowSize(move, Direction.SOUTH_EAST,
        Direction.NORTH_WEST);

    cell.setPiece(oldPiece);
    return eastWest + northEastSouthWest + southEastNorthWest;
  }

  private int getRowSize(int move, int direction1, int direction2)
  {
    int n1 = countRowEqualPieces(move, direction1);
    int n2 = countRowEqualPieces(move, direction2);
    return n1 + n2 + 1;
  }

  public int countRowEqualPieces(int move, int direction)
  {
    int count = 0;
    Cell cell = cells.get(move);
    for(Cell neighbor = cell.getNeighbor(direction);
        neighbor != null && neighbor.getPiece().equals(cell.getPiece());
        neighbor = neighbor.getNeighbor(direction))
      count++;
    return count;
  }

  public Point getPosition(int move)
  {
    Cell cell = cells.get(move);
    for(int i = 0; i < grid.length; i++)
    {
      for(int j = 0; j < grid[i].length; j++)
      {
        if(grid[i][j] != null && grid[i][j].equals(cell))
        {
          int row = i + 1;
          int column = j + 1;
          if(row > size)
            column -= row - size;
          return new Point(row, column);
        }
      }
    }
    return null;
  }

  //@todo: better testing
  private List<Cell> getCapturedCellsVisited;

  public List<Cell> getCapturedCells(int move, Piece piece)
  {
    Cell cell = cells.get(move);

    // Temporarily set the piece to validate
    Piece oldPiece = cell.getPiece();
    cell.setPiece(piece);

    List<Cell> capturedGroups = new ArrayList<Cell>();

    if(getCapturedCellsVisited == null)
      getCapturedCellsVisited = new ArrayList<Cell>();
    else
      getCapturedCellsVisited.clear();
    for(Cell neighbor : cell.getNeighbors())
    {
      if(neighbor != null
          && !neighbor.getPiece().equals(cell.getPiece())
          && !neighbor.getPiece().equals(Piece.EMPTY))
      {
        getCapturedCellsVisited.clear();
        if(!canReachEmpty(neighbor, getCapturedCellsVisited))
        {
          List<Cell> group = new ArrayList<Cell>();

          populateGroup(neighbor, group);
          capturedGroups.addAll(group);
        }
      }
    }
    cell.setPiece(oldPiece);
    return capturedGroups;
  }

  private List<Cell> groupGetCapturedCellsNumber;
  private int getCapturedCellsNumber(int move, Piece piece)
  {
    Cell cell = cells.get(move);

    // Temporarily set the piece to validate
    Piece oldPiece = cell.getPiece();
    cell.setPiece(piece);

    int captured = 0;

    if(getCapturedCellsVisited == null)
      getCapturedCellsVisited = new ArrayList<Cell>();
    else
      getCapturedCellsVisited.clear();
    for(Cell neighbor : cell.getNeighbors())
    {
      if(neighbor != null
          && !neighbor.getPiece().equals(cell.getPiece())
          && !neighbor.getPiece().equals(Piece.EMPTY))
      {
        if(!canReachEmpty(neighbor, getCapturedCellsVisited))
        {
          if(groupGetCapturedCellsNumber == null)
            groupGetCapturedCellsNumber = new ArrayList<Cell>();
          else
            groupGetCapturedCellsNumber.clear();

          populateGroup(neighbor, groupGetCapturedCellsNumber);
          captured += groupGetCapturedCellsNumber.size();
        }
      }
    }
    cell.setPiece(oldPiece);
    return captured;
  }

  private List<Cell> isSuicideVisited;
  public boolean isSuicide(int move, Piece piece)
  {
    Cell cell = cells.get(move);

    // Temporarily set the piece to validate
    Piece oldPiece = cell.getPiece();
    cell.setPiece(piece);

    if(isSuicideVisited == null)
      isSuicideVisited = new ArrayList<Cell>();
    else
      isSuicideVisited.clear();

    if(canReachEmpty(cell, isSuicideVisited))
    {
      cell.setPiece(oldPiece);
      return false;
    }
    cell.setPiece(oldPiece);
    return getCapturedCellsNumber(move, piece) == 0;
  }

  private boolean canReachEmpty(Cell cell, List<Cell> visited)
  {
    for(Cell neighbor : cell.getNeighbors())
    {
      if(neighbor != null)
      {
        if(neighbor.getPiece().equals(Piece.EMPTY))
          return true;
        if(neighbor.getPiece().equals(cell.getPiece()) && !visited.contains(
            neighbor))
        {
          visited.add(neighbor);
          if(canReachEmpty(neighbor, visited))
          {
            visited.remove(neighbor);
            return true;
          }
        }
      }
    }
    return false;
  }

  public void populateGroup(Cell cell, List<Cell> group)
  {
    Piece piece = cell.getPiece();
    group.add(cell);
    for(Cell neighbor : cell.getNeighbors())
    {
      if(neighbor != null
          && neighbor.getPiece().equals(piece)
          && !group.contains(neighbor))
      {
        populateGroup(neighbor, group);
      }
    }
  }
  
  @Override
  public String toString()
  {
    String output = "";
    for(int i = 0; i < grid.length; i++)
    {
      if(i < size)
      {
        for(int a = 0; a < size - i - 1; a++)
          output += " ";
      }
      for(int j = 0; j < grid[i].length; j++)
      {
        if(grid[i][j] == null)
          output += " ";
        else if(grid[i][j].getPiece().equals(Piece.EMPTY))
          output += "O ";
        else if(grid[i][j].getPiece().equals(Piece.BLACK))
          output += "B ";
        else if(grid[i][j].getPiece().equals(Piece.WHITE))
          output += "W ";
      }
      output += "\n";
    }
    return output;
  }

}
